/*
 * FreeRTOS V202011.00
 * Copyright (C) 2020 Amazon.com, Inc. or its affiliates.  All Rights Reserved.
 *
 * Copyright (c) 2023, Arm Limited and Contributors. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * https://www.FreeRTOS.org
 * https://aws.amazon.com/freertos
 *
 */

/**
 * @file mqtt_subscription_manager.c
 * @brief Functions for managing MQTT subscriptions.
 */

/* Standard includes. */
#include <stddef.h>
#include <string.h>

/* Subscription manager header include. */
#include "core_mqtt.h"
#include "mqtt_subscription_manager.h"

static bool isTopicAlreadyInSubscriptionList(const SubscriptionElement_t *pxSubscriptionList,
                                             const char *pcTopicFilterString,
                                             uint16_t usTopicFilterLength,
                                             const IncomingPubCallback_t pxIncomingPublishCallback,
                                             const void *pvIncomingPublishCallbackContext);
static size_t getAvailableSubscriptionListIndex(SubscriptionElement_t *pxSubscriptionList);

bool SubscriptionManager_AddSubscription(SubscriptionElement_t *pxSubscriptionList,
                                         const char *pcTopicFilterString,
                                         uint16_t usTopicFilterLength,
                                         IncomingPubCallback_t pxIncomingPublishCallback,
                                         void *pvIncomingPublishCallbackContext)
{
    if ((pxSubscriptionList == NULL) || (pcTopicFilterString == NULL) || (usTopicFilterLength == 0U)
        || (pxIncomingPublishCallback == NULL)) {
        LogError(("Invalid parameter. pxSubscriptionList=%p, pcTopicFilterString=%p,"
                  " usTopicFilterLength=%u, pxIncomingPublishCallback=%p.",
                  pxSubscriptionList,
                  pcTopicFilterString,
                  (unsigned int)usTopicFilterLength,
                  pxIncomingPublishCallback));

        return false;
    }

    /* If a subscription already exists, don't do anything. */
    if (isTopicAlreadyInSubscriptionList(pxSubscriptionList,
                                         pcTopicFilterString,
                                         usTopicFilterLength,
                                         pxIncomingPublishCallback,
                                         pvIncomingPublishCallbackContext)) {
        LogWarn(("Subscription already exists.\n"));
        return true;
    }

    size_t xStoreIndex = getAvailableSubscriptionListIndex(pxSubscriptionList);

    if (xStoreIndex >= SUBSCRIPTION_MANAGER_MAX_SUBSCRIPTIONS) {
        return false;
    }

    pxSubscriptionList[xStoreIndex].pcSubscriptionFilterString = pcTopicFilterString;
    pxSubscriptionList[xStoreIndex].usFilterStringLength = usTopicFilterLength;
    pxSubscriptionList[xStoreIndex].pxIncomingPublishCallback = pxIncomingPublishCallback;
    pxSubscriptionList[xStoreIndex].pvIncomingPublishCallbackContext = pvIncomingPublishCallbackContext;

    return true;
}

/*-----------------------------------------------------------*/

void SubscriptionManager_RemoveSubscription(SubscriptionElement_t *pxSubscriptionList,
                                            const char *pcTopicFilterString,
                                            uint16_t usTopicFilterLength)
{
    if ((pxSubscriptionList == NULL) || (pcTopicFilterString == NULL) || (usTopicFilterLength == 0U)) {
        LogError(("Invalid parameter. pxSubscriptionList=%p, pcTopicFilterString=%p,"
                  " usTopicFilterLength=%u.",
                  pxSubscriptionList,
                  pcTopicFilterString,
                  (unsigned int)usTopicFilterLength));
        return;
    }

    // Find and remove from list
    for (int32_t lIndex = 0; lIndex < SUBSCRIPTION_MANAGER_MAX_SUBSCRIPTIONS; lIndex++) {
        if (pxSubscriptionList[lIndex].usFilterStringLength == usTopicFilterLength) {
            if (strncmp(pxSubscriptionList[lIndex].pcSubscriptionFilterString, pcTopicFilterString, usTopicFilterLength)
                == 0) {
                memset(&(pxSubscriptionList[lIndex]), 0x00, sizeof(SubscriptionElement_t));
            }
        }
    }
}

/*-----------------------------------------------------------*/

bool SubscriptionManager_HandleIncomingPublishes(SubscriptionElement_t *pxSubscriptionList,
                                                 MQTTPublishInfo_t *pxPublishInfo)
{
    if ((pxSubscriptionList == NULL) || (pxPublishInfo == NULL)) {
        LogError(("Invalid parameter. pxSubscriptionList=%p, pxPublishInfo=%p,", pxSubscriptionList, pxPublishInfo));
    }

    bool isMatched = false;
    for (int32_t lIndex = 0; lIndex < SUBSCRIPTION_MANAGER_MAX_SUBSCRIPTIONS; lIndex++) {
        if (pxSubscriptionList[lIndex].usFilterStringLength > 0) {
            MQTT_MatchTopic(pxPublishInfo->pTopicName,
                            pxPublishInfo->topicNameLength,
                            pxSubscriptionList[lIndex].pcSubscriptionFilterString,
                            pxSubscriptionList[lIndex].usFilterStringLength,
                            &isMatched);

            if (isMatched == true) {
                pxSubscriptionList[lIndex].pxIncomingPublishCallback(
                    pxSubscriptionList[lIndex].pvIncomingPublishCallbackContext, pxPublishInfo);

                return true;
            }
        }
    }

    return false;
}

static bool isTopicAlreadyInSubscriptionList(const SubscriptionElement_t *pxSubscriptionList,
                                             const char *pcTopicFilterString,
                                             uint16_t usTopicFilterLength,
                                             const IncomingPubCallback_t pxIncomingPublishCallback,
                                             const void *pvIncomingPublishCallbackContext)
{
    /* Scans backwards to find duplicates. */
    for (int32_t lIndex = (int32_t)SUBSCRIPTION_MANAGER_MAX_SUBSCRIPTIONS - 1; lIndex >= 0; lIndex--) {
        if ((pxSubscriptionList[lIndex].usFilterStringLength == usTopicFilterLength)
            && (strncmp(pcTopicFilterString,
                        pxSubscriptionList[lIndex].pcSubscriptionFilterString,
                        (size_t)usTopicFilterLength)
                == 0)) {
            if ((pxSubscriptionList[lIndex].pxIncomingPublishCallback == pxIncomingPublishCallback)
                && (pxSubscriptionList[lIndex].pvIncomingPublishCallbackContext == pvIncomingPublishCallbackContext)) {
                return true;
            }
        }
    }

    return false;
}

static size_t getAvailableSubscriptionListIndex(SubscriptionElement_t *pxSubscriptionList)
{
    /* Start at end of array, so that we will insert at the first available index. */
    for (int32_t lIndex = SUBSCRIPTION_MANAGER_MAX_SUBSCRIPTIONS - 1; lIndex >= 0; lIndex--) {
        if (pxSubscriptionList[lIndex].usFilterStringLength == 0) {
            return lIndex;
        }
    }

    return SUBSCRIPTION_MANAGER_MAX_SUBSCRIPTIONS;
}
